package biz.ws.ws;

import biz.ws.ws.exception.UserContainerBusyException;
import io.netty.channel.Channel;
import lombok.Data;

import java.io.Serializable;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.function.Supplier;

/**
 * @创建人 xiaojiancheng
 * @时间 2020/1/7
 * @描述 $k$
 */
public class UserContainerImpl implements UserContainer {
    //  乐观锁允许的延迟时间
    long allowDelay = 30;

    public UserContainerImpl() {
    }

    public UserContainerImpl(long allowDelay) {
        this.allowDelay = allowDelay;
    }

    private ReadWriteLock rwLock = new ReentrantReadWriteLock();

    /**
     *  使用双表的方式，支持双向检索
     *  <li>    channel -> user
     *  <li>    userId -> user -> channelMap  此处会检索多条channel
     */
    private Map<Channel, MsgSession> channelMap = new HashMap<>();
    private Map<Serializable, MsgSession> userMap = new HashMap<>();

    int size;

    @Override
    public Channel put(MsgSession msgUser, Channel channel)  throws UserContainerBusyException {
        //  msgUser 描述用户登录信息
        //  如果容器中存在用户，将不会理会这个msgUser
        //  通常是在用户登录的时候调用这个方法
        BeanBox<Channel> box = new BeanBox<>();
        writeLock(() -> {
            MsgSession u0 = userMap.get(msgUser.getUser().getUserId());
            //  如果用户不存在，需要再userMap中添加一条记录 下次就可以检索到这条记录
            if(u0 == null){
                u0 = msgUser;
                userMap.put(msgUser.getUser().getUserId(),u0);
            }
            //  这里会出现重复登录的情况 这里不会自动移除
            channelMap.put(channel,u0);
            size++;

            //  重复登录
            Channel originChannel = u0.getInnerMap().put(msgUser.getLoginFrom(),channel);
            if(originChannel != null){
                //  在此处纠正上面的重复登录问题
                channelMap.remove(originChannel);
                //  删除的链接容量 -1
                size--;
            }
            box.setT(originChannel);
            return null;
        });
        return box.getT();
    }

    @Override
    public void putListUser(List<MsgSession> userList)  throws UserContainerBusyException{
        for (MsgSession item : userList) {
            userMap.put(item.getUser().getUserId(),item);
            item.setInnerMap(new HashMap<>());
        }
    }

    @Override
    public MsgSession get(Channel channel)  throws UserContainerBusyException{
        BeanBox<MsgSession> box = new BeanBox<>();
        readLock(() -> {
            MsgSession msgUser = channelMap.get(channel);
            box.setT(msgUser);
            return null;
        });
        return box.getT();
    }

    @Override
    public MsgSession get(Serializable id)  throws UserContainerBusyException{
        BeanBox<MsgSession> box = new BeanBox<>();
        readLock(() -> {
            box.setT(userMap.get(id));
            return null;
        });
        return box.getT();
    }

    @Override
    public MsgSession removeUser(MsgSession msgUser)  throws UserContainerBusyException{
        BeanBox<MsgSession> box = new BeanBox<>();
        writeLock(() -> {
            MsgSession u0 = userMap.get(msgUser.getUser().getUserId());
            if(u0 != null){
                //  删除channelMap中的数据 (内存泄漏问题)
                Collection<Channel> channels = u0.getInnerMap().values();
                for (Channel item : channels) {
                    channelMap.remove(item);
                    size--;
                    //  此处只充当容器  不会涉及到连接是否关闭  外部自己去解决这个问题
                }
            }
            box.setT(u0);
            return null;
        });
        return box.getT();
    }

    @Override
    public void removeConnection(Channel channel)  throws UserContainerBusyException{
        writeLock(()->{
            MsgSession remove = channelMap.remove(channel);
            if(remove != null){
                Set<String> set = remove.getInnerMap().keySet();
                for (String s : set) {
                    Channel channel1 = remove.getInnerMap().get(s);
                    if(channel == channel1){
                        remove.getInnerMap().remove(s);
                        break;
                    }
                }

                //  如果没有任何连接了 移除用户
                if(remove.getInnerMap().size() == 0){
                    userMap.remove(remove.getUser().getUserId());
                }
                size--;
            }

            return null;
        });
    }

    @Override
    public boolean hasConnection(Channel channel)  throws UserContainerBusyException{
        MsgSession msgUser = channelMap.get(channel);
        return msgUser != null;
    }

    @Override
    public boolean hasUser(MsgSession msgUser)  throws UserContainerBusyException{
        return userMap.get(msgUser.getUser().getUserId()) != null;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public Map<Serializable, MsgSession> userList()  throws UserContainerBusyException{
        return null;
    }

    private void writeLock(Supplier<Void> supplier) throws UserContainerBusyException {
        Lock wl = rwLock.writeLock();
        boolean hasLock = false;
        try {
            hasLock = wl.tryLock(allowDelay, TimeUnit.MICROSECONDS);
            if(hasLock){
                supplier.get();
                return ;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            if(hasLock){
                wl.unlock();
            }
        }

        //  如果获取锁失败  直接抛出异常
        throw new UserContainerBusyException("获取锁失败");
    }

    private void readLock(Supplier<Void> supplier) throws UserContainerBusyException {
        Lock rl = rwLock.readLock();
        boolean hasLock = false;
        try {
            hasLock = rl.tryLock(allowDelay, TimeUnit.MICROSECONDS);
            if(hasLock){
                supplier.get();
                return ;
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            if(hasLock){
                rl.unlock();
            }
        }

        //  如果获取锁失败  直接抛出异常
        throw new UserContainerBusyException("获取锁失败");
    }

    @Data
    private static class BeanBox<T>{
        T t;

        public BeanBox() {
        }
    }
}
